﻿using System;
using System.Collections.Generic;

namespace CoreEx.Database.Query.Expressions
{
    /// <summary>
    /// A query factory class that is capable of creating Select, Update,Insert and Delete queries.
    /// </summary>
    /// <seealso cref="DbSelectQuery"/>    
    public static class DbQueryFactory
    {        
        public static DbQuery<DbSelectExpression> Select(params Func<DbExpressionFactory,DbExpression>[] expressionSelector)
        {
            DbExpressionFactory expressionFactory = new DbExpressionFactory();
            var selectQuery = new DbSelectQuery();
            var expressions = new List<DbExpression>();
            foreach (var selector in expressionSelector)            
                expressions.Add(selector(expressionFactory));            
            selectQuery.QueryExpression.ProjectionExpression = expressionFactory.List(expressions);
            return selectQuery;
        }
    }

    /// <summary>
    /// Provides the common interface for implementing <see cref="DbQuery{TQueryExpression}"/>
    /// </summary>
    public abstract class DbQuery : DbExpression
    {
        /// <summary>
        /// Gets the <see cref="DbQueryType"/> for this <see cref="DbQuery{TQueryExpression}"/>.
        /// </summary>
        public abstract DbQueryType QueryType { get; }

        /// <summary>
        /// Returns the <see cref="DbExpression"/> that the <see cref="DbQuery{TQueryExpression}"/> implementation operates on.
        /// </summary>
        /// <returns></returns>
        public abstract DbExpression GetQueryExpression();

    }


    /// <summary>
    /// Provides the basic fluent interface for all query types.
    /// </summary>
    /// <typeparam name="TQueryExpression"></typeparam>
    public abstract class DbQuery<TQueryExpression> : 
        DbQuery where TQueryExpression:DbQueryExpression, new()
    {

        /// <summary>
        /// Initializes a new instance of the <see cref="DbQuery{TQueryExpression}"/> class.
        /// </summary>
        protected DbQuery()
        {
            QueryExpression = new TQueryExpression();
        }

        /// <summary>
        /// Gets the <see cref="DbExpressionType"/> of the <see cref="DbExpression"/>.
        /// </summary>
        /// <value></value>
        public override DbExpressionType ExpressionType
        {
            get { return DbExpressionType.Query; }
        }

        

        /// <summary>
        /// Gets or sets the <see cref="DbQueryExpression"/> that this <see cref="DbQuery{TQueryExpression}"/> operates on.
        /// </summary>
        public TQueryExpression QueryExpression { get; set; }

        public override DbExpression GetQueryExpression()
        {
            return QueryExpression;
        }


        /// <summary>
        /// Specifies the tables, views, derived tables, and joined tables used in DELETE, SELECT, and UPDATE statements.
        /// </summary>
        /// <param name="expressionSelector">A <see cref="Func{T,TResult}"/> to specify the <see cref="DbExpression"/>.</param>
        /// <returns><see cref="DbQuery{TQueryExpression}"/></returns>
        public DbQuery<TQueryExpression> From(Func<DbExpressionFactory,DbExpression> expressionSelector)
        {
            var dbExpression = expressionSelector(ExpressionFactory);                        
            if (!QueryExpression.FromExpression.IsNull())
                dbExpression = ExpressionFactory.List(new[] { QueryExpression.FromExpression, dbExpression });
            QueryExpression.FromExpression = dbExpression;                
            return this;
        }

        /// <summary>
        /// Creates a <see cref="DbJoinExpression"/> between the current 'FROM' clause and the expression returned by the <paramref name="target"/> functor.
        /// </summary>
        /// <param name="joinExpressionType">Specifies the type of join expression.</param>
        /// <param name="target">The target join expression.</param>
        /// <param name="condition">A <see cref="DbExpression"/> that specifies the join condition.</param>
        /// <returns><see cref="DbQuery{TQueryExpression}"/></returns>
        public DbQuery<TQueryExpression> Join(DbJoinExpressionType joinExpressionType, Func<DbExpressionFactory,DbExpression> target, Func<DbExpressionFactory,DbExpression> condition)
        {
            var targetExpression = target(ExpressionFactory);
            var conditionExpression = condition(ExpressionFactory);
            var dbExpression = (DbExpression)ExpressionFactory.MakeJoin(joinExpressionType, targetExpression, conditionExpression);
            if (!QueryExpression.FromExpression.IsNull())
                dbExpression = ExpressionFactory.Concat(dbExpression, QueryExpression.FromExpression);
            QueryExpression.FromExpression = dbExpression;
            return this;
        }

        /// <summary>
        /// Creates an 'INNER JOIN' between the current 'FROM' clause and the expression returned by the <paramref name="target"/> functor.
        /// </summary>
        /// <param name="target">The target join expression.</param>
        /// <param name="condition">A <see cref="DbExpression"/> that specifies the join condition.</param>
        /// <returns><see cref="DbQuery{TQueryExpression}"/></returns>
        public DbQuery<TQueryExpression> InnerJoin(Func<DbExpressionFactory,DbExpression> target, Func<DbExpressionFactory,DbExpression> condition)
        {
            return Join(DbJoinExpressionType.InnerJoin, target, condition);
        }

        /// <summary>
        /// Creates an 'LEFT OUTER JOIN' between the current 'FROM' clause and the expression returned by the <paramref name="target"/> functor.
        /// </summary>
        /// <param name="target">The target join expression.</param>
        /// <param name="condition">A <see cref="DbExpression"/> that specifies the join condition.</param>
        /// <returns><see cref="DbQuery{TQueryExpression}"/></returns>
        public DbQuery<TQueryExpression> LeftOuterJoin(Func<DbExpressionFactory, DbExpression> target, Func<DbExpressionFactory, DbExpression> condition)
        {
            return Join(DbJoinExpressionType.LeftOuterJoin, target, condition);
        }

        /// <summary>
        /// Creates an 'RIGHT OUTER JOIN' between the current 'FROM' clause and the expression returned by the <paramref name="target"/> functor.
        /// </summary>
        /// <param name="target">The target join expression.</param>
        /// <param name="condition">A <see cref="DbExpression"/> that specifies the join condition.</param>
        /// <returns><see cref="DbQuery{TQueryExpression}"/></returns>
        public DbQuery<TQueryExpression> RightOuterJoin(Func<DbExpressionFactory, DbExpression> target, Func<DbExpressionFactory, DbExpression> condition)
        {
            return Join(DbJoinExpressionType.LeftOuterJoin, target, condition);
        }


        /// <summary>
        /// Specifies the search condition for the rows returned by the query.
        /// </summary>
        /// <param name="expressionSelector">A <see cref="Func{T,TResult}"/> to specify the <see cref="DbExpression"/>.</param>
        /// <returns><see cref="DbQuery{TQueryExpression}"/></returns>
        public DbQuery<TQueryExpression> Where(Func<DbExpressionFactory,DbExpression> expressionSelector)
        {
            var dbExpression = expressionSelector(ExpressionFactory);
            if (!QueryExpression.WhereExpression.IsNull())
                dbExpression = ExpressionFactory.And(QueryExpression.WhereExpression, dbExpression);
            QueryExpression.WhereExpression = dbExpression;
            return this;
        }


       
    }
}
